package com.alibaba.fastjson2.internal.processor;

import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StructInfo {
    final int modifiers;
    final boolean referenceDetect;
    final boolean disableJSONB;
    final boolean disableAutoType;
    final boolean disableArrayMapping;

    String typeKey;
    int readerFeatures;
    int writerFeatures;
    final TypeElement element;
    final String name;
    final String binaryName;
    final Map<String, AttributeInfo> attributes = new LinkedHashMap<>();

    public StructInfo(
            Types types,
            TypeElement element,
            DeclaredType jsonCompiledDeclaredType,
            DeclaredType jsonTypeDeclaredType,
            String name,
            String binaryName
    ) {
        this.element = element;
        this.name = name;
        this.binaryName = binaryName;

        this.modifiers = Analysis.getModifiers(element.getModifiers());

        AnnotationMirror jsonType = null;
        for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
            DeclaredType annotationType = mirror.getAnnotationType();
            if (types.isSameType(annotationType, jsonTypeDeclaredType)) {
                jsonType = mirror;
            }
        }

        boolean referenceDetect = true,
                disableJSONB = false,
                disableAutoType = false,
                disableArrayMapping = false;
        if (jsonType != null) {
            for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : jsonType.getElementValues().entrySet()) {
                String annFieldName = entry.getKey().getSimpleName().toString();
                AnnotationValue value = entry.getValue();
                switch (annFieldName) {
                    case "disableReferenceDetect":
                        referenceDetect = !(Boolean) value.getValue();
                        break;
                    case "disableJSONB":
                        disableJSONB = (Boolean) value.getValue();
                        break;
                    case "disableAutoType":
                        disableAutoType = (Boolean) value.getValue();
                        break;
                    case "disableArrayMapping":
                        disableArrayMapping = (Boolean) value.getValue();
                        break;
                    default:
                        break;
                }
            }
        }

        this.referenceDetect = referenceDetect;
        this.disableJSONB = disableJSONB;
        this.disableAutoType = disableAutoType;
        this.disableArrayMapping = disableArrayMapping;
    }

    public AttributeInfo getAttributeByField(String name, VariableElement field) {
        AttributeInfo attr = attributes.get(name);
        TypeMirror type = field.asType();

        if (attr == null) {
            attr = new AttributeInfo(name, field.asType(), field, null, null, null);
            AttributeInfo origin = attributes.putIfAbsent(name, attr);
            if (origin != null) {
                attr = origin;
            }
        }

        attr.field = field;
        return attr;
    }

    public AttributeInfo getAttributeByMethod(
            String name,
            TypeMirror type,
            ExecutableElement getter,
            ExecutableElement setter
    ) {
        AttributeInfo attr = attributes.get(name);
        if (attr == null) {
            attr = new AttributeInfo(name, type, null, getter, setter, null);
            AttributeInfo origin = attributes.putIfAbsent(name, attr);
            if (origin != null) {
                attr = origin;
            }
        }

        if (getter != null) {
            attr.getMethod = getter;
        }
        if (setter != null) {
            attr.setMethod = setter;
        }

        return attr;
    }

    public List<AttributeInfo> getReaderAttributes() {
        return attributes.values()
                .stream()
                .filter(AttributeInfo::supportSet)
                .sorted()
                .collect(Collectors.toList());
    }
}
